package com.yntx.usercenter.common.config.sharding;

import com.google.common.collect.Range;
import com.yntx.usercenter.common.config.TenantUserContextHolder;
import com.yntx.usercenter.common.config.sharding.enums.ShardingTableCacheEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import java.util.*;
import java.util.function.Function;

/**
 * 用户表精确切分算法
 *
 * @author jielihaofeng
 * @date 2023-04-29 02:46
 */
@Slf4j
public class UserTablePreciseShardingAlgorithm implements StandardShardingAlgorithm<Long> {

    private final String TABLE_SPLIT_SYMBOL = "_";

    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
        String logicTableName = rangeShardingValue.getLogicTableName();

        ShardingTableCacheEnum logicTable = ShardingTableCacheEnum.of(logicTableName);

        // between and 的起始值
        Range<Long> valueRange = rangeShardingValue.getValueRange();
        boolean hasLowerBound = valueRange.hasLowerBound();
        boolean hasUpperBound = valueRange.hasUpperBound();

        // 获取最大值和最小值
        Set<String> tableNameCache = logicTable.resultTableNamesCache();
        Long min = hasLowerBound ? valueRange.lowerEndpoint() : getLowerEndpoint(logicTableName, tableNameCache);
        Long max = hasUpperBound ? valueRange.upperEndpoint() : getUpperEndpoint(logicTableName, tableNameCache);

        // 循环计算分表范围
        Set<String> resultTableNames = new LinkedHashSet<>();
        while (min.compareTo(max) < 0 || min.equals(max)) {
            String tableName = logicTableName + TABLE_SPLIT_SYMBOL + min;
            resultTableNames.add(tableName);
            min++;
        }

        return ShardingAlgorithmTool.getShardingTablesAndCreate(logicTable, resultTableNames);
    }

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        String logicTableName = preciseShardingValue.getLogicTableName();

        // fixme:貌似现在拿不到上下文？从租户上下文中拿租户配置，如果租户的是否共享用户开关打开，这里不走分表，直接返回逻辑表对应的表
        Boolean isShareUser = TenantUserContextHolder.get().getIsShareUser();
        if (isShareUser != null && isShareUser) {
            return logicTableName;
        }

        ShardingTableCacheEnum logicTable = ShardingTableCacheEnum.of(logicTableName);
        // 格式为 表名_#{system_code}
        String newTableName = logicTableName + "_" + preciseShardingValue.getValue().toString();
        log.info("表名为:" + newTableName);

        // 动态新增节点
        if (!collection.contains(newTableName)) {
            collection.add(newTableName);
        }
        // 动态创建表和刷新分表节点缓存
        return ShardingAlgorithmTool.getShardingTableAndCreate(logicTable, newTableName);
    }

    @Override
    public void init() {

    }

    @Override
    public String getType() {
        return null;
    }

    /**
     * 获取 最小分片值
     *
     * @param tableNames     表名集合
     * @param logicTableName 逻辑表名
     * @return 最小分片值
     */
    private Long getLowerEndpoint(String logicTableName, Collection<String> tableNames) {
        Optional<Long> optional = tableNames.stream()
                .map(o -> Long.parseLong(o.replace(logicTableName + TABLE_SPLIT_SYMBOL, "")))
                .min(Comparator.comparing(Function.identity()));
        if (optional.isPresent()) {
            return optional.get();
        } else {
            log.error(">>>>>>>>>> 【ERROR】获取数据最小分表失败，请稍后重试，tableName：{}", tableNames);
            throw new IllegalArgumentException("获取数据最小分表失败，请稍后重试");
        }
    }

    /**
     * 获取 最大分片值
     *
     * @param tableNames     表名集合
     * @param logicTableName 逻辑表名
     * @return 最大分片值
     */
    private Long getUpperEndpoint(String logicTableName, Collection<String> tableNames) {
        Optional<Long> optional = tableNames.stream()
                .map(o -> Long.parseLong(o.replace(logicTableName + TABLE_SPLIT_SYMBOL, "")))
                .max(Comparator.comparing(Function.identity()));
        if (optional.isPresent()) {
            return optional.get();
        } else {
            log.error(">>>>>>>>>> 【ERROR】获取数据最大分表失败，请稍后重试，tableName：{}", tableNames);
            throw new IllegalArgumentException("获取数据最大分表失败，请稍后重试");
        }
    }

}